/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package edu.yonsei.iwb.srf.server;

import com.hp.hpl.jena.vocabulary.RDF;
import edu.yonsei.iwb.sdt.SDTLearner;
import edu.yonsei.iwb.sdt.datastructure.tree.DEdge;
import edu.yonsei.iwb.sdt.datastructure.tree.DNode;
import edu.yonsei.iwb.sdt.datastructure.tree.NodeType;
import edu.yonsei.iwb.sdt.ontology.PropertyArc;
import edu.yonsei.iwb.sdt.refinement.CardinalityRestrictionRefinement;
import edu.yonsei.iwb.sdt.refinement.ConceptConstructorRefinement;
import edu.yonsei.iwb.sdt.refinement.ConjunctionRefinement;
import edu.yonsei.iwb.sdt.refinement.DisjunctionRefinement;
import edu.yonsei.iwb.sdt.refinement.DomainRestrictionRefinement;
import edu.yonsei.iwb.sdt.refinement.ExpandingHistory;
import edu.yonsei.iwb.sdt.refinement.QualificationRefinement;
import edu.yonsei.iwb.sdt.refinement.Refinement;
import edu.yonsei.iwb.sdt.refinement.RefinementType;
import edu.yonsei.iwb.sdt.refinement.ValueInfoOfDP;
import edu.yonsei.iwb.sdt.util.SDTLog;
import edu.yonsei.iwb.srf.BuildForestDriver;
import edu.yonsei.iwb.srf.TIBootstrapper;
import edu.yonsei.iwb.ts.TSSearch;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;

/**
 *
 * @author dk
 */
public class SDTLearningHandler extends Thread {

    private boolean flag = false;
    private Socket socket;
    private BufferedReader br = null;
    private PrintWriter pw = null;

    public SDTLearningHandler(Socket socket) throws IOException {
        this.socket = socket;
        br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
    }

    @Override
    public void run() {
        System.out.println("New Connection from " + socket.toString());
        try {
            StringBuilder sb = new StringBuilder();
            while (true) {
                sb.append(br.readLine());
                if (!br.ready()) {
                    break;
                }
            }
            String request = sb.toString();
            XContentParser jsonResult = JsonXContent.jsonXContent.createParser(request);
            Map<String, Object> resultMap = jsonResult.mapOrdered();
            String mode = resultMap.get("-m").toString();
            if (mode.equals("sdt")) {
                learn(resultMap);
            } else if (mode.equals("dp")) {
                createData(resultMap);
            }
        } catch (IOException ex) {
            Logger.getLogger(SDTLearningHandler.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                br.close();
                pw.close();
                socket.close();
                System.out.println("# " + socket.toString() + " is closed..");

            } catch (IOException ex) {
                Logger.getLogger(SDTLearningHandler.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    private void learn(Map<String, Object> resultMap) throws IOException {
//            ArrayList<LinkedHashMap> type = (ArrayList) resultMap.get("rows");
        String runID = resultMap.get("runID").toString();
        int slavenum = (int) resultMap.get("slave");
        int treenum = (int) resultMap.get("treenum");
        String targetClass = resultMap.get("tc").toString();
        String targetProperty = resultMap.get("tp").toString();
        double refsr = (double) resultMap.get("refsr");
        int featureSelectionType = (int) resultMap.get("fstype");
        boolean searchMethod = (boolean) resultMap.get("searchMethod");
        
        Object indexname = resultMap.get("indexname");
        ArrayList indexList = (ArrayList) indexname;
        TSSearch.setINDEX(indexList);

        System.out.println("#      target class : " + targetClass);
        System.out.println("#   target property : " + targetProperty);
        System.out.println("#             slave : " + slavenum);
        System.out.println("#           treenum : " + treenum);
        System.out.println("#             refsr : " + refsr);
        System.out.println("#           fs type : " + featureSelectionType);
        System.out.println("#     search method : " + searchMethod);

        String dataloc = resultMap.get("dataloc").toString();
        int tisize = (int) resultMap.get("tisize");
        
        TIBootstrapper tib = new TIBootstrapper(dataloc);
        
        ArrayList<Hashtable<Integer, ArrayList<String>>> tiset = null;        
        if(tisize == -1){
            // tisize가 -1이면 원래 training 수 만큼 그대로 인스턴스 리스트 생성
            tiset = tib.getInstanceListOfEachSlave(treenum);
        }else{
            tiset = tib.getInstanceListOfEachSlave(treenum, tisize); // 각 tree마다 타겟 인스턴스 셋 취합            
        }

        ArrayList<DNode> resultSet = new ArrayList();
        Hashtable<Integer, int[]> candNumTable = new Hashtable();

        for (int i = 0; i < treenum; i++) {
            System.out.println("# " + i + " th tree learning start");
            ArrayList<String> insset = new ArrayList();
            ArrayList<Integer> vset = new ArrayList();
            Hashtable<Integer, ArrayList<String>> tiAndValue = tiset.get(i);

            Enumeration<Integer> values = tiAndValue.keys();
            while (values.hasMoreElements()) {
                int value = values.nextElement();
                ArrayList<String> tis = tiAndValue.get(value);
                insset.addAll(tis);
                for (int j = 0; j < tis.size(); j++) {
                    vset.add(value);
                }
            }
            
            System.out.println("# " + i + " th input data is generated");

            SDTLearner sdtl = new SDTLearner(targetClass, targetProperty, insset, vset, refsr, featureSelectionType, searchMethod);            
            
            sdtl.setENTROPY_THRESHOLD(0);
            SDTLog.setLogType(SDTLog.DETAIL);
            DNode result = sdtl.learningStart();
            int nodeid = result.NODE_ID;
            int[] candnumarray = sdtl.getCandNumArray();
            candNumTable.put(nodeid, candnumarray);
            resultSet.add(result);
            System.out.println("# " + i + " th tree learning complete");
        }

        String nresult = returnResultString(slavenum, resultSet, candNumTable);
        printOut(slavenum, nresult, runID);
        pw.println(nresult);
        pw.flush();

    }

    private void createData(Map<String, Object> resultMap) {
        System.out.println("# do data partition");
        String dataLocID = resultMap.get("dataloc").toString();

        String training = resultMap.get("training").toString();
        String test = resultMap.get("test").toString();
        String value = resultMap.get("value").toString();

        File dir = new File("./dataset/" + dataLocID);
        dir.mkdirs();
        File trainf = new File("./dataset/" + dataLocID + "/training.tsv");
        File testf = new File("./dataset/" + dataLocID + "/test.tsv");
        File vlist = new File("./dataset/" + dataLocID + "/vlist.tsv");
        try {
            PrintWriter pw1 = new PrintWriter(new OutputStreamWriter(new FileOutputStream(trainf), "euc-kr"));
            pw1.write(training);
            pw1.flush();
            pw1.close();

            PrintWriter pw2 = new PrintWriter(new OutputStreamWriter(new FileOutputStream(testf), "euc-kr"));
            pw2.write(test);
            pw2.flush();
            pw2.close();

            PrintWriter pw3 = new PrintWriter(new OutputStreamWriter(new FileOutputStream(vlist), "euc-kr"));
            pw3.write(value);
            pw3.flush();
            pw3.close();
            
            pw.println(socket.toString()+" - data partition complete");
            pw.flush();

        } catch (UnsupportedEncodingException ex) {
            Logger.getLogger(SDTLearningHandler.class.getName()).log(Level.SEVERE, null, ex);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(SDTLearningHandler.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    private void printOut(int slavenum, String resultMsg, String runID) {
        PrintWriter pwtj = null;
        try {
            File dir = new File("./trees/slave/" + runID);
            dir.mkdirs();
            File tfile = new File("./trees/slave/" + runID + "/" + slavenum + "_trees.json");
            tfile.createNewFile();
            pwtj = new PrintWriter(new OutputStreamWriter(new FileOutputStream(tfile), "euc-kr"));
            pwtj.write(resultMsg);
            pwtj.flush();
            pwtj.close();
        } catch (UnsupportedEncodingException ex) {
            Logger.getLogger(BuildForestDriver.class.getName()).log(Level.SEVERE, null, ex);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(BuildForestDriver.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(SDTLearningHandler.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            pwtj.close();
        }
    }

    private String returnResultString(int slavenum, ArrayList<DNode> trees, Hashtable<Integer, int[]> candNumTable) {
        String result = new String();
        try {
            XContentBuilder doc = XContentFactory.jsonBuilder().prettyPrint().startObject();
            doc.startObject("set");
            doc.field("size", trees.size());
            doc.field("slavenum", slavenum);
            for (int i = 0; i < trees.size(); i++) {
                ArrayList<String> nodelist = new ArrayList();
                DNode tree = trees.get(i);
                doc.startObject("id" + slavenum + "_" + i);
                doc.field("rootid", tree.NODE_ID);
                int[] s = candNumTable.get(tree.NODE_ID);
//                System.out.println(" -----------------------------  " + s[0] +" ____ " + s[1]);
                doc.field("candnum", candNumTable.get(tree.NODE_ID));
                addTreeToResult(tree, doc, nodelist);
                doc.field("nodelist", nodelist);
                doc.endObject();
            }
            doc.endObject().endObject();
            result += doc.string();
        } catch (IOException ex) {
            Logger.getLogger(SDTLearningHandler.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            return result;
        }
    }

    private void addTreeToResult(DNode node, XContentBuilder doc, ArrayList<String> nodelist) {
        try {
            String nodeid = "n_" + node.NODE_ID;
            doc.startObject(nodeid);
            if (!nodelist.contains(nodeid)) {
                nodelist.add(nodeid);
            }
            doc.field("entropy", node.getEntropy());
            doc.field("noderef", node.getRefinementName());
            doc.field("nodeid", node.NODE_ID);
            doc.field("nodetype", node.NODE_TYPE);
            if (node.NODE_TYPE == NodeType.LEAFNODE) {
                doc.field("nodeclass", node.getFinalClassLabel());
            }

//            Vector<TargetInstance> tiset = node.getInstanceSet();
//
//            ArrayList<String> tiIDs = new ArrayList();
//            ArrayList<String> tiURIs = new ArrayList();
//            ArrayList<Integer> tiValues = new ArrayList();
//
//            for (int i = 0; i < tiset.size(); i++) {
//                TargetInstance ti = tiset.get(i);
//                tiIDs.add(ti.getId());
//                tiURIs.add(ti.getUri());
//                tiValues.add(ti.getTargetvalue());
//            }

//            doc.field("tiIDs", tiIDs);
//            doc.field("tiURIs", tiURIs);
//            doc.field("tiValues", tiValues);

            doc.field("cnsize", node.getEdge().size());
            for (int i = 0; i < node.getEdge().size(); i++) {
                DEdge edge = node.getEdge().get(i);
                doc.startObject("child_" + i)
                        .field("cnid", edge.getChildNode().NODE_ID)
                        .field("edgetype", edge.getEdgeType())
                        .field("refname", edge.getRefinement().getName());
                writeRefinementInfo(doc, edge.getRefinement());
                doc.endObject();
            }
            doc.endObject();

            for (int i = 0; i < node.getEdge().size(); i++) {
                DEdge edge = node.getEdge().get(i);
                addTreeToResult(edge.getChildNode(), doc, nodelist);
            }

        } catch (IOException ex) {
            Logger.getLogger(SDTLearningHandler.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    private void writeRefinementInfo(XContentBuilder doc, Refinement ref) throws IOException {
        ExpandingHistory eh = ref.getExpandingHistory();
        if (eh != null) {
            Vector<PropertyArc> paset = eh.getPropertySet();
            Vector<String> qset = eh.getQuantifierSet();
            ArrayList<String> pset = new ArrayList();
            ArrayList<Boolean> pdset = new ArrayList();
            for (int j = 0; j < paset.size(); j++) {
                PropertyArc pa = paset.get(j);
                pset.add(pa.getProperty());
                pdset.add(pa.getDirection());
            }
            doc.field("pset", pset);
            doc.field("pdset", pdset);
            doc.field("qset", qset);
            doc.field("expanded", true);
        } else {
            doc.field("expanded", false);
        }
        int type = ref.getRefinementType();
        doc.field("reftype", type);

        if (type == RefinementType.CONCEPTCONSTRUCTOR) {
            //multi 이면서 negation인 경우 추가해야함
            if (((ConceptConstructorRefinement) ref).isMulti()) {
                doc.field("conset", ((ConceptConstructorRefinement) ref).getSubset());
                doc.field("multi", true);
            } else {
                doc.field("con", ((ConceptConstructorRefinement) ref).getConditionResource());
                doc.field("multi", false);
            }
        } else if (type == RefinementType.CONJUNCTION) {
            doc.field("cen", ((ConjunctionRefinement) ref).getConditionr());
            doc.field("con", ((ConjunctionRefinement) ref).getMateConditionr());
        } else if (type == RefinementType.DISJUNCTION) {
            doc.field("cen", ((DisjunctionRefinement) ref).getConditionr());
            doc.field("con", ((DisjunctionRefinement) ref).getMateConditionr());
        } else {
            if (type == RefinementType.CARDINALITYRESTRICTION) {
                doc.field("property", ((CardinalityRestrictionRefinement) ref).getConditionp().getProperty());
                doc.field("pdir", ((CardinalityRestrictionRefinement) ref).getConditionp().getDirection());
                doc.field("standard", ((CardinalityRestrictionRefinement) ref).getStandard());
            } else if (type == RefinementType.QUALIFICATION) {
                doc.field("cen", ((QualificationRefinement) ref).getConditionResource());
            } else if (type == RefinementType.DOMAINRESTRICTION) {
                int dtype = ((DomainRestrictionRefinement) ref).getViotype();
                doc.field("dtype", dtype);
                doc.field("property", ((DomainRestrictionRefinement) ref).getProperty());
                if (dtype == ValueInfoOfDP.DATARANGE) {
                    if (ref.getBranchingType()) {
                        doc.field("standard", ((DomainRestrictionRefinement) ref).getStandard());
                    } else {
                        doc.field("datarange", ((DomainRestrictionRefinement) ref).getDatarange());
                    }
                } else {
                    doc.field("prefix", ((DomainRestrictionRefinement) ref).getPrefix());
                    doc.field("standard", ((DomainRestrictionRefinement) ref).getStandard());
                }
            }
        }

    }
}
